// @ts-nocheck
import type { TreeUtilConfig } from './type'

export class TreeUtils {
  /**
   * 节点中唯一标识符属性的名称（默认值为“id”）
   */
  private readonly idProp: string
  /**
   * 节点中节点名称属性的名称（默认值为“name”）
   */
  private readonly nameProp: string
  /**
   * 节点中父节点id属性的名称（默认值为“parentId”）
   */
  private readonly parentIdProp: string

  /**
   * 存储父祖id属性的属性的名称（默认值为“parentsId”）
   */
  private readonly parentsIdProp: string

  /**
   * 存储父祖名称的属性的名称（默认值为“parentsName”）
   */
  private readonly parentsNameProp: string
  /**
   * 存储子节点的属性的名称（默认值为“children”）
   */
  private readonly childrenProp: string
  /**
   * 存储深度的属性的名称（默认值为“level”）
   */
  private readonly levelProp: string

  /**
   * 构造函数
   * @param config - 配置选项，如果省略了配置选项，则使用默认选项
   */
  constructor(config?: TreeUtilConfig) {
    this.idProp = config?.idProp || 'id'
    this.nameProp = config?.nameProp || 'name'
    this.levelProp = config?.levelProp || 'level'
    this.parentIdProp = config?.parentIdProp || 'parentId'
    this.parentsIdProp = config?.parentsIdProp || 'parentsId'
    this.parentsNameProp = config?.parentsNameProp || 'parentsName'
    this.childrenProp = config?.childrenProp || 'children'
  }

  /**
   * 将列表转换为树状结构
   * @param list list of objects, objects need to have id (as you configured, or 'id' by default) and parentId property (as you configured, or 'parentId' by default)
   * @param parentId id of parent node
   */
  list2Tree(list: any[], parentId: any = null): any[] {
    return TreeUtils.deepCopy(list)
      .filter((item: any) => item[this.parentIdProp] === parentId)
      .map((item: any) => {
        const ret = { ...item }
        const children = this.list2Tree(list, item[this.idProp])
        if (children && children.length > 0) {
          ret[this.childrenProp] = children
        }
        return ret
      })
  }

  /**
   * 将树状结构转换为列表
   * @param tree tree of objects, objects need to have children (as you configured, or 'children' by default) and parentId property (as you configured, or 'parentId' by default)
   * @param parentId
   */
  tree2List(tree: any[], parentId: any = null): any[] {
    const ret = TreeUtils.deepCopy(tree).reduce((acc: any, curr: any) => {
      const { [this.childrenProp]: children, ...rest } = curr
      return [
        ...acc,
        { ...rest, [this.parentIdProp]: parentId },
        ...(children && children.length ? this.tree2List(children, rest[this.idProp]) : []),
      ]
    }, [])
    return ret
  }

  /**
   * 按给定id在树结构中查找节点的方法
   * @param tree - 树结构数据
   * @param id - 节点id
   * @returns
   */
  findTreeNodeById(tree: any[], id: any): any {
    return this.findTreeNode(tree, (item) => item[this.idProp] === id)
  }

  /**
   * 通过给定的回调函数在树结构中查找节点的方法
   * @param tree - 树结构数据
   * @param fn - 用于查找节点的回调函数
   * @returns
   * @example
   * ```ts
   * utils.findTreeNode(tree, item => item.id === myId);
   * ```
   */
  findTreeNode(tree: any[], fn: (item: any) => boolean): any {
    const node = tree.find((item) => fn(item))
    if (node) {
      return node
    }
    return tree.reduce((acc, curr) => acc || this.findTreeNode(curr[this.childrenProp] || [], fn), null)
  }

  /**
   * 方法，通过给定的回调函数查找树结构中的所有节点
   * @param tree - 树结构数据
   * @param fn - 用于查找所有节点的回调函数
   * @returns
   * @example
   * ```ts
   * utils.findAllTreeNodes(tree, item => item.id === myId);
   * ```
   */
  findAllTreeNodes(tree: any[], fn: (item: any) => boolean): any {
    const nodes = tree.filter((item) => fn(item))
    return tree.reduce(
      (acc, curr) => [
        ...acc,
        ...(curr[this.childrenProp].length ? this.findAllTreeNodes(curr[this.childrenProp], fn) : []),
      ],
      nodes,
    )
  }

  /**
   * 按给定id删除树中节点的方法（可变操作！
   * @param tree - 树结构数据
   * @param id - 节点id
   */
  deleteNode(tree: any[], id: any): any {
    const index = tree.findIndex((item) => item[this.idProp] == id)
    if (index != -1) {
      return tree.splice(index, 1)[0]
    }
    return tree.reduce((acc, curr) => acc || this.deleteNode(curr[this.childrenProp], id), null)
  }

  /**
   * 向树中添加新节点的方法（可变操作！
   * @param tree - 树结构数据
   * @param parentId - 父节点的id，如果新节点应该在根级别，则为null
   * @param childData - 新节点的数据
   */
  addNode(tree: any[], parentId: any, childData: any): void {
    if (parentId == null) {
      tree.push(childData)
      return
    }
    const index = tree.findIndex((item) => item[this.idProp] == parentId)
    if (index != -1) {
      tree[index][this.childrenProp].push({ [this.childrenProp]: [], ...childData })
      return
    }
    tree.forEach((item) => this.addNode(item[this.childrenProp], parentId, childData))
  }

  /**
   * 用树中给定的数据按id更新节点的方法（可变操作！）
   * @param tree - 树结构数据
   * @param id - 节点id
   * @param data - 节点的新数据（如果您想保留它，还应该传递子节点）
   */
  editNode(tree: any[], id: any, data: any): void {
    const index = tree.findIndex((item) => item[this.idProp] == id)
    if (index != -1) {
      tree[index] = { [this.idProp]: tree[index][this.idProp], [this.childrenProp]: [], ...data }
      return
    }
    tree.forEach((item) => this.editNode(item[this.childrenProp], id, data))
  }

  /**
   * 在树结构中查找给定节点的所有子节点的方法
   * @param tree - 树结构数据
   * @param id - 节点id
   * @returns
   */
  findAllChildrenNodes(tree: any[], id: any): any[] {
    const node = this.findTreeNodeById(tree, id)
    return node
      ? [
          ...node[this.childrenProp],
          ...node[this.childrenProp].reduce((acc: any, curr: any) => [...acc, ...this.getChildrenNodes(curr)], []),
        ]
      : []
  }

  /**
   * 递归获取树结构中给定节点的所有子节点的Helper方法
   * @param node - 节点
   * @returns
   * @private
   */
  private getChildrenNodes(node: any): any[] {
    return [
      ...node[this.childrenProp],
      ...node[this.childrenProp].reduce((acc: any, curr: any) => [...acc, ...this.getChildrenNodes(curr)], []),
    ]
  }

  /**
   * 在树结构中查找给定节点的所有父节点的方法
   * @param tree - 树结构数据
   * @param nodeOrKey - 节点或节点id
   * @returns
   */
  findAllParentNodes(tree: any[], nodeOrKey: any): any[] {
    const parents: any[] = []
    let parent = this.findNodeParent(tree, nodeOrKey)
    while (parent) {
      parents.push(parent)
      parent = this.findNodeParent(tree, parent[this.idProp])
    }
    return parents.reverse()
  }

  /**
   * 在树结构中查找给定节点的父节点的方法
   * @param tree - 树结构数据
   * @param nodeOrKey - 节点或节点id
   * @param __parent - 父节点 (仅用于递归)
   * @returns
   */
  findNodeParent(tree: any[], nodeOrKey: any): any {
    const nodeId = typeof nodeOrKey === 'string' ? nodeOrKey : nodeOrKey[this.idProp]
    if (!nodeId) {
      return null
    }
    let parent: any = null
    this.walker(
      tree,
      (n, i, p) => {
        if (n[this.idProp] === nodeId) {
          parent = p || null
          return true
        }
      },
      'breadth',
      true,
    )
    return parent
  }

  /**
   * 映射单个节点及其子节点
   * @param node
   * @param {(node, parents: any[]) => any} mapFunction
   * @returns {any}
   */
  mapNode(node: any, mapFunction: (node: any, parents: any[]) => any, __parents: any[] = []): any {
    const mappedNode: Node = mapFunction({ ...node }, __parents)

    if (this.hasChildren(node)) {
      const children = node[this.childrenProp].map((n) => this.mapNode(n, mapFunction, [...__parents, mappedNode]))
      mappedNode[this.childrenProp] = children as any
    }
    return mappedNode
  }

  /**
   * 映射多个节点及其子节点
   * @param {any[]} nodes
   * @param {(node, parents: any[]) => any} mapFunction
   * @returns {any[]}
   */
  mapNodes(nodes: any[], mapFunction: (node: any, parents: any[]) => any, __parents: any[] = []): any[] {
    return nodes.map((node) => this.mapNode(node, mapFunction, __parents))
  }

  /**
   * 节点增强, 自动为每个节点增加 level、parentsId 、parentsName、title、label 属性
   * @param {any[]} nodes
   * @param parentsReverse     parentsId[]和parentsName[] 是否中颠倒其中的数据
   */
  enhanceNodes(nodes: any[], parentsReverse = true): any[] {
    return this.mapNodes(nodes, (node, parents) => {
      const ret = node
      const parentsName = parents ? parents.map((t) => t[this.nameProp]) : []
      const parentsId = parents ? parents.map((t) => t[this.idProp]) : []

      ret[this.parentsNameProp] = parentsReverse ? parentsName.reverse() : parentsName
      ret[this.parentsIdProp] = parentsReverse ? parentsId.reverse() : parentsId
      ret[this.levelProp] = parents ? parents.length : 0
      if (!ret['label']) {
        ret['label'] = ret[this.nameProp]
      }
      if (!ret['title']) {
        ret['title'] = ret['label']
      }
      return ret
    })
  }

  /**
   * 筛选单个节点及其子节点。
   * @param node  树结构数据
   * @param {(node: any, parents: any[]) => boolean} predicate
   * @param mode      遍历时深度优先或宽度优先。默认深度优先
   * @returns {any}
   */
  filterNode(
    node: any,
    predicate: (node: any, parents: any[]) => boolean,
    mode: 'depth' | 'breadth' = 'depth',
    __parents: any[] = [],
  ): any {
    let res: any = null
    const isNodeItself = predicate(node, __parents)
    if (mode === 'breadth' && !isNodeItself) {
      return null
    }

    const filteredChildren = this.isBranch(node)
      ? node[this.childrenProp]
          .map((childNode) => this.filterNode(childNode, predicate, mode, [...__parents, node]))
          .filter((i) => i !== null)
      : null
    const hasChildren = filteredChildren && filteredChildren.length > 0

    if (isNodeItself || hasChildren) {
      const childrenData = filteredChildren ? { [this.childrenProp]: filteredChildren } : {}
      res = Object.assign({}, node, childrenData)
    }
    return res
  }

  /**
   * 筛选多个节点及其子节点
   * @param {any[]} nodes 树结构数据
   * @param {(node: any, parents: any[]) => boolean} predicate
   * @param mode      遍历时深度优先或宽度优先。默认深度优先
   * @returns {any[]}
   */
  filterNodes(
    nodes: any[],
    predicate: (node: any, parents: any[]) => boolean,
    mode: 'depth' | 'breadth' = 'depth',
    __parents: any[] = [],
  ): any[] {
    const result: any = nodes.map((node) => this.filterNode(node, predicate, mode, __parents)).filter((i) => i !== null)

    return result
  }

  /**
   * 遍历树中的每个节点
   * @param {any[]} nodes 树结构数据
   * @param {(node: any, index: number, parent: any, level: number) => (boolean | void)} iterator   迭代器, 如果breakable 和 返回值 都为true将中断遍历
   * @param {"depth" | "breadth"} mode    遍历时深度优先或宽度优先。默认深度优先
   * @param {boolean} breakable 迭代器返回true时是否允许中断遍历
   * @returns {boolean}
   */
  walker(
    nodes: any[],
    iterator: (node: any, index: number, parent: any, level: number) => boolean | void,
    mode: 'depth' | 'breadth' = 'depth',
    breakable = false,
    __parent?: any,
    __level = 0,
  ) {
    let breakFlag
    let found = nodes.some((node, index: number) => {
      breakFlag = iterator(node, index, __parent, __level)
      if (mode === 'depth' && !breakFlag && this.hasChildren(node)) {
        breakFlag = this.walker(node[this.childrenProp], iterator, mode, breakable, node, __level + 1)
      }
      return breakFlag
    })

    if (mode === 'breadth' && (!breakable || !found)) {
      found = nodes.some((node, index: number) => {
        if (this.hasChildren(node)) {
          breakFlag = this.walker(node[this.childrenProp], iterator, mode, breakable, node, __level + 1)
          return breakFlag
        }
      })
    }

    if (breakable) {
      return !!found
    }
  }

  /**
   * 获取给定键或节点的同级和自身
   * @param {any[]} nodes
   * @param nodeOrKey
   * @returns {any[]}
   */
  siblingsAndSelf(nodes: any[], nodeOrKey: any): any[] {
    const nodeId = typeof nodeOrKey === 'string' ? nodeOrKey : nodeOrKey[this.idProp]
    if (!nodeId) {
      return []
    }

    let siblings: any[] = []
    this.walker(
      nodes,
      (cur, index, parent) => {
        if (cur[this.idProp] === nodeId) {
          siblings = parent ? parent[this.childrenProp] : nodes
        }
      },
      'depth',
      true,
    )
    return siblings
  }

  /**
   * 获取给定键或节点的同级(不包含自身)
   * @param {any[]} nodes
   * @param nodeOrKey
   * @returns {any[]}
   */
  siblings(nodes: any[], nodeOrKey: any): any[] {
    const nodeId = typeof nodeOrKey === 'string' ? nodeOrKey : nodeOrKey[this.idProp]
    if (!nodeId) {
      return []
    }
    const siblingsAndSelf: any[] = this.siblingsAndSelf(nodes, nodeId)
    return siblingsAndSelf.filter((node) => node[this.idProp] !== nodeId)
  }

  /**
   * 获取给定键或节点的同级的前一个节点
   * @param {any[]} nodes
   * @param nodeOrKey
   * @returns {any}
   */
  prevSibling(nodes: any[], nodeOrKey: any): any {
    const nodeId = typeof nodeOrKey === 'string' ? nodeOrKey : nodeOrKey[this.idProp]
    if (!nodeId) {
      return null
    }
    const siblingsAndSelf: any[] = this.siblingsAndSelf(nodes, nodeId)
    let prev: any = null
    siblingsAndSelf.some((node, index) => {
      if (index > 0 && node[this.idProp] === nodeId) {
        prev = siblingsAndSelf[index - 1]
        return true // break some()
      }
    })
    return prev
  }

  /**
   * 获取给定键或节点的同级的前面的所有节点
   * @param {any[]} nodes
   * @param nodeOrKey
   * @returns {any[]}
   */
  prevSiblings(nodes: any[], nodeOrKey: any): any[] {
    const nodeId = typeof nodeOrKey === 'string' ? nodeOrKey : nodeOrKey[this.idProp]
    if (!nodeId) {
      return []
    }
    const siblingsAndSelf: any[] = this.siblingsAndSelf(nodes, nodeId)
    const currIndex: number = this.indexOf(nodes, nodeId, siblingsAndSelf)
    return siblingsAndSelf.slice(0, Math.max(0, currIndex))
  }

  /**
   * 获取给定键或节点的同级的后一个节点
   * @param {any[]} nodes
   * @param nodeOrKey
   * @returns {any}
   */
  nextSibling(nodes: any[], nodeOrKey: any): any {
    const nodeId = typeof nodeOrKey === 'string' ? nodeOrKey : nodeOrKey[this.idProp]
    if (!nodeId) {
      return null
    }
    const siblingsAndSelf: any[] = this.siblingsAndSelf(nodes, nodeId)
    let next: any = null
    if (siblingsAndSelf && siblingsAndSelf.length > 0) {
      siblingsAndSelf.some((node, index) => {
        if (index < siblingsAndSelf.length - 1 && node[this.idProp] === nodeId) {
          next = siblingsAndSelf[index + 1]
          return true // break some()
        }
      })
    }

    return next
  }

  /**
   * 获取给定键或节点的同级的后面的所有节点
   * @param {any[]} nodes
   * @param nodeOrKey
   * @returns {any[]}
   */
  nextSiblings(nodes: any[], nodeOrKey: any): any[] {
    const nodeId = typeof nodeOrKey === 'string' ? nodeOrKey : nodeOrKey[this.idProp]
    if (!nodeId) {
      return []
    }
    const siblingsAndSelf: any[] = this.siblingsAndSelf(nodes, nodeId)
    const currIndex: number = this.indexOf(nodes, nodeId, siblingsAndSelf)
    return currIndex < 0 ? [] : siblingsAndSelf.slice(currIndex + 1)
  }

  /**
   * 获取给定键或节点的下面4点的内容
   * (1) 自身
   * (2) 同级  (children会被去除)
   * (3) 父祖节点
   * (4) 父祖节点的同级  (children会被去除)
   *
   * @param {any[]} nodes
   * @param nodeOrKey
   * @returns {any[]}
   */
  parentsAndSelfSiblings(nodes: any[], nodeOrKey?: any): any[] {
    const enhancedNodes = this.enhanceNodes(nodes)
    if (!nodeOrKey) {
      return nodes.map((item) => {
        const { children, ...rest } = item
        return { ...rest }
      })
    }
    const nodeId = typeof nodeOrKey === 'string' ? nodeOrKey : nodeOrKey[this.idProp]
    if (!nodeId) {
      return []
    }
    const node = this.findTreeNodeById(enhancedNodes, nodeId)
    const parentsId = node[this.parentsIdProp]
    const ret: any[] = []
    this.walker(
      enhancedNodes,
      (item, index, parent, level) => {
        if (!parent || parentsId.includes(parent[this.idProp])) {
          const { children, ...rest } = item
          ret.push(rest)
        }
      },
      'breadth',
      false,
    )

    return this.list2Tree(ret)
  }

  /**
   * 检查给定节点是否为第一个子节点
   * @param {any[]} nodes
   * @param nodeOrKey
   * @returns {boolean}
   */
  isFirstChild(nodes: any[], nodeOrKey: any): boolean {
    const nodeId = typeof nodeOrKey === 'string' ? nodeOrKey : nodeOrKey[this.idProp]
    if (!nodeId) {
      return false
    }
    const siblings = this.siblingsAndSelf(nodes, nodeId)
    if (!siblings || siblings.length === 0) {
      return false
    }
    return siblings[0][this.idProp] === nodeId
  }

  /**
   * 检查给定节点是否为最后一个子节点
   * @param {any[]} nodes
   * @param nodeOrKey
   * @returns {boolean}
   */
  isLastChild(nodes: any[], nodeOrKey: any): boolean {
    const nodeId = typeof nodeOrKey === 'string' ? nodeOrKey : nodeOrKey[this.idProp]
    if (!nodeId) {
      return false
    }
    const siblings = this.siblingsAndSelf(nodes, nodeId)
    if (!siblings || siblings.length === 0) {
      return false
    }
    return siblings[siblings.length - 1][this.idProp] === nodeId
  }

  /**
   * 获取节点在数结构中的深度, 根节点下为0
   * @param {any[]} nodes
   * @param nodeOrKey
   * @returns {number}
   */
  getLevel(nodes: any[], nodeOrKey: any): number {
    let level = -1
    const nodeId = typeof nodeOrKey === 'string' ? nodeOrKey : nodeOrKey[this.idProp]
    if (!nodeId) {
      return level
    }
    this.walker(
      nodes,
      (item, index, parent, itemLevel) => {
        if (item[this.idProp] === nodeId) {
          level = itemLevel
          return true
        }
      },
      'breadth',
      true,
    )
    return level
  }

  /**
   * 获取节点的索引值(同级内的)
   * @param {any[]} nodes
   * @param nodeOrKey
   * @param siblings
   * @returns {number}
   */
  indexOf(nodes: any[], nodeOrKey: any, siblings?: any[]): number {
    const nodeId = typeof nodeOrKey === 'string' ? nodeOrKey : nodeOrKey[this.idProp]
    let i = -1
    if (!nodeId) {
      return i
    }
    const nodeSiblingsAndSelf = siblings || this.siblingsAndSelf(nodes, nodeOrKey)
    nodeSiblingsAndSelf.some((sibling, index) => {
      if (nodeId === sibling[this.idProp]) {
        i = index
        return true // return true to stop next;
      }
    })
    return i
  }

  /**
   * 检查节点是否包含子节点
   * @param node
   * @returns {boolean}
   * ```typescript
   * tree.hasChildren({ key: 'bar' }); // return false
   * tree.hasChildren({ key: 'bar', children: [] }); // return false
   * tree.hasChildren({ key: 'bar', children: [{ key: '...'}] }); // return true
   * ```
   * */
  hasChildren(node: any): boolean {
    if (!node) {
      return false
    }
    const children = node[this.childrenProp]
    return !!children && children.length > 0
  }

  /**
   * 检查节点是否具有children属性（无论children的长度是否为0）
   * @param node
   * @returns {boolean}
   * @example
   * ```typescript
   * tree.isBranch({id: '' }); // return false
   * tree.isBranch({id: '', children: [] }); // return true
   * */
  isBranch(node: any): boolean {
    if (!node) {
      return false
    }
    const children = node[this.childrenProp]
    return !!children && children.length >= 0
  }

  /**
   * 深度克隆
   * @param obj - 要克隆的对象
   * @returns
   */
  static deepCopy(obj: any): any {
    return JSON.parse(JSON.stringify(obj))
  }
}

export const treeUtilsInstance = new TreeUtils()
